/*
 * ==========================================================================================================
 * 
 *                                               Octree demo
 *                                              
 * ==========================================================================================================
 * 
 * This demo shows the system used to subdivide the space, the octree.
 * Each scene is inside an octree, and each time an object is added the engine put it in an octree branch.
 * In this scene some objects are moving in circle with the octree drawn, you can see the space subdivisions.
 * This algorithm is used to optimize the rendering. (see http://en.wikipedia.org/wiki/Octree)
 * 
 * 
 * 
 * - Engine control
 * Espace - quit
 * F1 - view console
 * F2 - toggle fullscreen mode
 * F3 - antialiasing
 * F4 - view node model
 * F5 - view collisions
 * F6 - collision fillmode
 * F7 - view partitioner
 * 
 * - Demo control
 * W - move forward
 * S - move backward
 * A - strafe left
 * D - strafe right
 * Left shift - move down
 * Space - move up
 * Mouse move - look
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Octree
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {

        #region fields

        // engine and render
        private DARE.CDare m_Dare;
        private DARE.CScene m_scene;
        private DARE.CRender3D m_render;

        // camera
        private DARE.CFirstPersonCamera m_firstCam;
        private Vector3 m_moveCam;

        // key state
        private bool m_rightButtonPressed;
        private bool m_leftButtonPressed;

        // ref node to test search in the octree
        private DARE.CNode m_refNode;
        private List<DARE.CNode> m_nodes;
        private DARE.CNode m_rot;

        private DARE.EType m_type = DARE.EType.STATIC;

        #endregion

        #region ctor

        public Game1()
        {
            // create the engine
            m_Dare = new DARE.CDare(this);
        }

        protected override void Initialize()
        {
            m_moveCam = Vector3.Zero;
            m_rightButtonPressed = false;
            m_leftButtonPressed = false;
            //m_drawOctree = true;
            base.Initialize();
        }

        #endregion

        #region load/unload content

        protected override void LoadContent()
        {
            // general configuration
            m_Dare.Config.General.WindowTitle = "Space Partitioner";
            m_Dare.Config.General.ShowMouseCursor = true;
            m_Dare.Config.General.ShowDebugConsole = true;

            // video configuration
            m_Dare.Config.Video.SetResolution(1024, 768);
            m_Dare.Config.Video.FullScreen = false;
            m_Dare.Config.Video.MultiSampling = true;
            m_Dare.Config.Video.UnlockFps = true;

            // start the engine
            m_Dare.RockNRoll();

            // scene creation
            CreateRender();
            CreateCamera();
            CreateScene();
        }

        #endregion

        #region scene creation

        private void CreateRender()
        {
            // simple use, only one scene and one render
            m_scene = new DARE.CScene("scene");
            m_render = new DARE.CRender3D("render", new DARE.COctree(-10, 60, -10, 60, -60, 10, 1));
            //m_render = new DARE.CRender3D("render", new DARE.COctree(-10, 60, -10, 60, -60, 10, 5));

            // register our renderer to the scene
            m_scene.RegisterRenderer(m_render);
        }

        private void CreateCamera()
        {
            // create our first person camera
            m_firstCam = new DARE.CFirstPersonCamera("firstCam");
            m_firstCam.Translate(new Vector3(25, 25, -25));
            m_firstCam.LookAt(new Vector3(50, 0, -50));

            // register the camera
            m_render.CameraMgr.RegisterCamera(m_firstCam);
        }

        private void CreateScene()
        {
            // set the light position
            //m_render.EffectMgr.LightPosition = new Vector4(-30, 50, 50, 0);
            //m_render.EffectMgr.AmbientIntensity = 0.1f;

            m_render.EffectMgr.LighteningEnabled = true;

            m_render.EffectMgr.AmbientLightColor = Color.White;
            m_render.EffectMgr.AmbientLightIntensity = 0.1f;

            m_render.EffectMgr.DirectionalLight1Direction = new Vector3(-0.6f, -1, -0.8f);
            m_render.EffectMgr.DirectionalLight1DiffuseColor = Color.White;
            m_render.EffectMgr.DirectionalLight1DiffuseIntensity = 1.0f;

            // display a welcome message
            DisplayWelcomeMessage();

            // hide the node origin
            m_render.ShowModelNode = false;

            // display a custom floor and sky in the scene
            DARE.CNode floor = m_render.CreateNode(DARE.EType.STATIC);

            // it seems that if the first point is not the negative one it will bug with collisions (z have to be negative in the first part, 
            // not in the second one)
            floor.AABB = new BoundingBox(new Vector3(0, -0.5f, -50), new Vector3(50, 0.5f, 0));
            //floor.AABB = new BoundingBox(new Vector3(0, -0.5f, 0), new Vector3(50, 0.5f, -50)); // WRONG
            floor.Entity.AttachModel(new DARE.CFloorModel());
            floor.Entity.Model.FillMode = FillMode.WireFrame;
            floor.Entity.Model.CullMode = CullMode.None;
            //floor.Entity.AttachSprite3D(new DARE.CLineText3D("info"));
            //((DARE.CLineText3D)floor.Entity.GetSprite3D("info")).Offset = Vector3.UnitY;
            //((DARE.CLineText3D)floor.Entity.GetSprite3D("info")).Text = floor.Name;

            //// because the info will not be set, because it's already in the octree
            //m_render.RemoveNode(floor);
            //m_render.AddNode(floor);

            //DARE.CNode physicFloor = floor.CreateChild(DARE.EType.STATIC).Translate(new Vector3(25, 0, -25));

            //physicFloor.AABB = new BoundingBox(new Vector3(-27, -2, 27), new Vector3(27, 2, -27));
            //physicFloor.Entity.CreatePhysicBox(50, 1, 50);
            //physicFloor.Entity.Physic.Body.Immovable = true;

            //m_render.OriginNode.Entity.AttachModel(new DARE.CFloorModel());
            //m_render.OriginNode.Entity.Model.FillMode = FillMode.WireFrame;
            //m_render.OriginNode.Entity.Model.CullMode = CullMode.None;
            //m_render.OriginNode.Entity.CreatePhysicBox(50, 0.5f, 50);
            //m_render.OriginNode.Entity.Physic.Body.Immovable = true;
            m_render.SkyMgr.CreateSkybox("Sky", "sky/ct");
            //m_render.SkyMgr.CreateSkybox("Sky", "CubeMaps/Brightday1/brightday1");

            //m_render.ShowCollision = true;

            //m_Dare.Config.Time.TimeSpeed = 0.1f;

            // octree test
            OctreeTest();
        }

        private DARE.CNode m_follow;

        private void OctreeTest()
        {
            // params
            int staticNumber = 0;
            int dynamicNumber = 0;
            int movingNumber = 10;

            // create some nodes
            m_nodes = new List<DARE.CNode>();
            m_rot = null;
            DARE.CNode cur = null;

            for (int i = 0; i < staticNumber; ++i)
                PopBox(DARE.EType.STATIC);
            for (int i = 0; i < dynamicNumber; ++i)
                PopSphere(DARE.EType.DYNAMIC);

            m_follow = null;
            for (int i = 0; i < movingNumber; ++i)
            {
                if (m_rot == null)
                {
                    m_rot = m_render.CreateNode(m_type).SetPosition(25, 25, -25).Roll(0.5f);
                    //m_rot.IsVisible = false;
                    cur = m_rot;
                }
                else
                {
                    cur = m_rot.CreateChild(m_type);
                    if (i == 1)
                        m_follow = cur.CreateChild(DARE.EType.DYNAMIC, false).TranslateY(0.5f).Yaw(0.1f);
                    cur.Yaw(i * 0.04f);
                    cur.TranslateX(15, DARE.CNode.ESpace.LOCAL);
                }
                cur.Entity.CreateBasicBox(0.5f, 0.5f, 0.5f);
                cur.Entity.Model.FillMode = FillMode.Solid;

                //DARE.CLineText3D sprite = new DARE.CLineText3D("text");
                //sprite.Offset = Vector3.UnitY / 2;
                //cur.Entity.AttachSprite3D(sprite);

                //sprite.Text = "Hein??";

                m_nodes.Add(cur);
            }

            //m_firstCam.AttachToNode(follow);
        }

        #endregion

        #region run

        private Matrix m_view = new Matrix();
        private Matrix m_proj = new Matrix();
        private BoundingFrustum m_frustrum = new BoundingFrustum(Matrix.Identity);

        protected override void Update(GameTime gameTime)
        {
            ProcessKeyPressed();
            ProcessKeyReleased();
            ProcessMousePressed();
            ProcessMouseReleased();
            ProcessMouseMoved();
            ProcessMouseWheel();
            ProcessRayCastCameraToMouse();
            ApplyMovement();

            int r = m_render.Partitioner.RegularNumber;
            int b = m_render.Partitioner.BackToFrontNumber;
            int f = m_render.Partitioner.FrontToBackNumber;

            int res = r + b + f;

            m_Dare.DebugMgr.DebugConsole.WriteLine("processed = " + res.ToString() + "\n");
            //m_Dare.DebugMgr.DebugConsole.WriteLine("node = " + m_render.NodeNumber);
            //m_Dare.DebugMgr.DebugConsole.WriteLine("static = " + m_render.StaticNodeNumber);
            //m_Dare.DebugMgr.DebugConsole.WriteLine("dynamic = " + m_render.DynamicNodeNumber);

            if (m_rot != null)
                m_rot.Yaw(0.0005f * m_Dare.Config.Time.TimeSinceLastFrame);

            base.Update(gameTime);
        }

        private void ProcessRayCastCameraToMouse()
        {
            foreach (DARE.CRayCast.CIntersectInfo iInfo in m_render.RayCast.IntersectAllFromCenterToMouse(100, 10))
            {
                if (iInfo.node.Entity.Model != null)
                {
                    iInfo.node.Entity.Model.Fade();

                    string fade;
                    if (iInfo.node.Entity.Model.FadingState == DARE.AModel.EFadingState.NONE) fade = "NONE";
                    else if (iInfo.node.Entity.Model.FadingState == DARE.AModel.EFadingState.IN) fade = "IN";
                    else if (iInfo.node.Entity.Model.FadingState == DARE.AModel.EFadingState.END) fade = "END";
                    else if (iInfo.node.Entity.Model.FadingState == DARE.AModel.EFadingState.OUT) fade = "OUT";
                    else fade = "UNKNOWN STATE";
                    m_Dare.DebugMgr.DebugConsole.WriteLine("AModel " + iInfo.node.Name + " fade " + fade);
                }
                //m_Dare.DebugMgr.DebugConsole.WriteLine("intersect: " + iInfo.node.Name + " at " + iInfo.pos.ToString());
            }
        }

        private void ApplyMovement()
        {
            float fixedTime = m_Dare.Config.Time.FixedTimeSinceLastFrame;
            float fixedMoveSpeed = fixedTime / 200.0f;
            float fixedRotateSpeed = fixedTime / 500.0f;

            if (m_rightButtonPressed)
                m_firstCam.Translate(m_moveCam * fixedMoveSpeed * 2);
        }

        private Texture2D dare = null;

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            if (dare == null)
                dare = DARE.CResourceMgr.Load<Texture2D>("Logo/DARE_small");
            m_Dare.SpriteBatch.Begin();
            m_Dare.SpriteBatch.Draw(dare, new Rectangle(0, 0, (int)(dare.Width / 1.5f), (int)(dare.Height / 1.5f)), Color.White);
            m_Dare.SpriteBatch.End();
        }

        #endregion

        #region input

        private void PopBox(DARE.EType type)
        {
            int minRand = 1;
            int maxRand = 49;

            Random rand = m_Dare.Random;
            DARE.CNode current = new DARE.CNode(type);

            current.Translate(
                rand.Next(minRand * 10, maxRand * 10) / 10.0f,
                rand.Next(minRand * 10, maxRand * 10) / 10.0f,
                -rand.Next(minRand * 10, maxRand * 10) / 10.0f);
            current.Entity.CreateBasicBox(0.6f, 0.6f, 0.6f);
            //current.Entity.CreatePhysicSphere();
            //current.Entity.Physic.Body.Immovable = true;

            m_nodes.Add(current);

            // add sprite3d for debug
            //DARE.CLineText3D sprite = new DARE.CLineText3D("text");
            //sprite.Offset = Vector3.UnitY / 2;
            //current.Entity.AttachSprite3D(sprite);

            //sprite.Text = "Hu??";

            m_render.AddNode(current);

            // add some physic
            current.Entity.CreatePhysicBox(0.7f, 0.7f, 0.7f);
            current.Entity.Physic.Body.Immovable = true;
        }

        private void PopSphere(DARE.EType type)
        {
            int minRand = 1;
            int maxRand = 49;

            Random rand = m_Dare.Random;
            DARE.CNode current = new DARE.CNode(type);

            current.Translate(
                rand.Next(minRand * 10, maxRand * 10) / 10.0f,
                rand.Next(minRand * 10, maxRand * 10) / 10.0f,
                -rand.Next(minRand * 10, maxRand * 10) / 10.0f);
            current.Entity.CreateBasicSphere(0.9f);

            m_nodes.Add(current);

            //DARE.CLineText3D sprite = new DARE.CLineText3D("text");
            //sprite.Offset = Vector3.UnitY / 2;
            //current.Entity.AttachSprite3D(sprite);

            //sprite.Text = "Quoi??";

            m_render.AddNode(current);

            //current.Entity.CreatePhysicSphere();
            //current.Entity.Physic.Body.Immovable = true;
        }

        private void ProcessKeyPressed()
        {
            foreach (Keys key in m_Dare.InputMgr.Keyboard.KeyPressedEvent)
                switch (key)
                {
                    // exit
                    case Keys.Escape: this.Exit(); break;

                    // video settings
                    case Keys.F1:
                        if (m_Dare.Config.Video.FullScreen)
                            m_Dare.Config.Video.SetResolution(1024, 768);
                        else
                            m_Dare.Config.Video.SetResolution(1680, 1050);
                        m_Dare.Config.Video.FullScreen = !m_Dare.Config.Video.FullScreen;
                        break;
                    case Keys.F4: m_Dare.Config.Video.MultiSampling = !m_Dare.Config.Video.MultiSampling; break;

                    case Keys.T:
                        foreach (DARE.CNode node in m_nodes)
                            node.Entity.Model.IsTransparent = true;
                        break;
                    case Keys.Y:
                        foreach (DARE.CNode node in m_nodes)
                            node.Entity.Model.IsTransparent = false;
                        break;

                    case Keys.OemPlus:
                        foreach (DARE.CNode node in m_nodes)
                            node.Entity.Model.Transparency += 0.1f;
                        break;

                    case Keys.OemMinus:
                        foreach (DARE.CNode node in m_nodes)
                            node.Entity.Model.Transparency -= 0.1f;
                        break;

                    case Keys.E: PopBox(DARE.EType.STATIC); break;
                    case Keys.R: PopSphere(DARE.EType.DYNAMIC); break;

                    case Keys.C:
                        if (m_firstCam.AttachedToNode)
                            m_firstCam.DetachFromNode();
                        else
                            m_firstCam.AttachToNode(m_follow);
                        break;

                    // display usage
                    //case Keys.Tab: DisplayUsage(); break;

                    // show node model
                    case Keys.N: m_render.ShowModelNode = !m_render.ShowModelNode; break;
                    case Keys.D1: m_render.FillMode = FillMode.Solid; break;
                    case Keys.D2: m_render.FillMode = FillMode.WireFrame; break;
                    case Keys.D3: m_render.ShowCollision = !m_render.ShowCollision; break;
                    case Keys.D4: m_render.CollisionFillMode = FillMode.Solid; break;
                    case Keys.D5: m_render.CollisionFillMode = FillMode.WireFrame; break;
                    case Keys.D6: m_render.Partitioner.Show = !m_render.Partitioner.Show; break;
                    case Keys.D7: m_render.Partitioner.ShowItems = !m_render.Partitioner.ShowItems; break;

                    case Keys.D8: m_firstCam.ManualAspectRatio = !m_firstCam.ManualAspectRatio; break;
                    case Keys.D9: m_firstCam.AspectRatio += 0.1f; break;
                    case Keys.D0: m_firstCam.AspectRatio -= 0.1f; break;
                    case Keys.O: m_firstCam.FovY += 0.1f; break;
                    case Keys.P: m_firstCam.FovY -= 0.1f; break;

                    case Keys.X:
                        if (m_type == DARE.EType.STATIC)
                            m_type = DARE.EType.DYNAMIC;
                        else if (m_type == DARE.EType.DYNAMIC)
                            m_type = DARE.EType.STATIC;
                        foreach (DARE.CNode node in m_nodes)
                            node.Type = m_type;
                        break;

                    // camera
                    case Keys.W: m_moveCam.Z -= 1; break;
                    case Keys.S: m_moveCam.Z += 1; break;
                    case Keys.A: m_moveCam.X -= 1; break;
                    case Keys.D: m_moveCam.X += 1; break;
                    case Keys.LeftShift: m_moveCam.Y -= 1; break;
                    case Keys.Space: m_moveCam.Y += 1; break;

                    default: break;
                }
        }

        private void ProcessKeyReleased()
        {
            foreach (Keys key in m_Dare.InputMgr.Keyboard.KeyReleasedEvent)
                switch (key)
                {
                    // camera
                    case Keys.W: m_moveCam.Z += 1; break;
                    case Keys.S: m_moveCam.Z -= 1; break;
                    case Keys.A: m_moveCam.X += 1; break;
                    case Keys.D: m_moveCam.X -= 1; break;
                    case Keys.LeftShift: m_moveCam.Y += 1; break;
                    case Keys.Space: m_moveCam.Y -= 1; break;

                    default: break;
                }
        }

        private void ProcessMousePressed()
        {
            foreach (DARE.CMouse.EButton button in m_Dare.InputMgr.Mouse.ButtonPressedEvent)
            {
                switch (button)
                {
                    case DARE.CMouse.EButton.RIGHT: m_rightButtonPressed = true; break;
                    case DARE.CMouse.EButton.LEFT: m_leftButtonPressed = true; break;

                    default: break;
                }
                m_Dare.InputMgr.Mouse.IsCentered = m_rightButtonPressed || m_leftButtonPressed;
                m_Dare.Config.General.ShowMouseCursor = !(m_rightButtonPressed || m_leftButtonPressed);
            }
        }

        private void ProcessMouseReleased()
        {
            foreach (DARE.CMouse.EButton button in m_Dare.InputMgr.Mouse.ButtonReleasedEvent)
            {
                switch (button)
                {
                    case DARE.CMouse.EButton.RIGHT: m_rightButtonPressed = false; break;
                    case DARE.CMouse.EButton.LEFT: m_leftButtonPressed = false; break;

                    default: break;
                }
                m_Dare.InputMgr.Mouse.IsCentered = m_rightButtonPressed || m_leftButtonPressed;
                m_Dare.Config.General.ShowMouseCursor = !(m_rightButtonPressed || m_leftButtonPressed);
            }
        }

        private void ProcessMouseMoved()
        {
            Vector2 mouseMoved = m_Dare.InputMgr.Mouse.MoveEvent.Movement;

            // first cam orientation
            if (m_Dare.InputMgr.Mouse.HasMoved && m_rightButtonPressed)
                m_firstCam.Rotate(new Vector3(-mouseMoved.Y / 400.0f, -mouseMoved.X / 400.0f, 0.0f));
            if (m_Dare.InputMgr.Mouse.HasMoved && m_leftButtonPressed)
            {

            }
        }

        private void ProcessMouseWheel()
        {
        }

        #endregion

        #region display methods

        private void DisplayWelcomeMessage()
        {
            m_Dare.DebugMgr.DebugConsole.WriteLine(
                "===========================================================================\n" +
                "Welcome to the octree test area !\n" +
                "      TAB - display usage.\n" +
                "===========================================================================\n");
        }

        private void DisplayUsage()
        {
            m_Dare.DebugMgr.DebugConsole.WriteLine(
                "===========================================================================\n" +
                "Usage:\n" +
                "mouse right button - mouse move\n" +
                "=>  rotate camera\n" +
                "mouse right button - w, s, a, d, space, shift\n" +
                "=>  camera movements\n" +
                "mouse left button - mouse move\n" +
                "=>  nothing atm\n" +
                "mouse left button - w, s, a, d, space, shift\n" +
                "=>  move the ref node, yaw orientation is relative to the camera\n" +
                "0\n" +
                "=>  process NOTHING in the octree\n" +
                "1\n" +
                "=>  process POINT in the octree\n" +
                "2\n" +
                "=>  process BOX in the octree\n" +
                "3\n" +
                "=>  process SPHERE in the octree\n" +
                "4\n" +
                "=>  process FRUSTRUM in the octree\n" +
                "R\n" +
                "regular order\n" +
                "F\n" +
                "front to back order\n" +
                "B\n" +
                "back to front order\n" +
                "===========================================================================\n");
        }

        private void DisplayNodeInfo(DARE.CNode node)
        {
            m_Dare.DebugMgr.DebugConsole.WriteLine(
                node.Name + " camera node selected !" +
                "\nposition      = " + node.Position.ToString() +
                "\norientation  = " + node.Orientation.ToString() +
                "\nscale          = " + node.NScale.ToString() + "\n");
        }

        #endregion

    }
}
